ETL Palmer Penguins

Roberto Baca

10/07/2025

ETL: Extraccion, Transformación y Carga

Que es un ETL ?

  • Extract: traer los datos.

  • Transform: limpiar, preparar y transformar.

  • Load: cargar o dejar listos para análisis o modelos.

En este ejercicio trabajermos con el dataset “Palmer Penguins”. El dataset penguins contiene datos sobre tres especies de pingüinos del archipielago Palmer (Antártida), con variables como:

  • especie (species)

  • isla (island)

  • largo y ancho del pico

  • largo y ancho de la aleta

  • masa corporal

  • sexo

  • año

Especies de pingüinos de las Islas Palmer
Especies de pingüinos de las Islas Palmer
#install.packages("leaflet")
library(leaflet)

# Ubicacion de la estacion palmer
# -64.77416897295598, -64.05381917264792

# Mapa interactivo
leaflet() %>%
  addTiles() %>%
  addMarkers(lat = -64.77416897295598, lng = -64.05381917264792, popup = "Estacion Palmer")
# librerias
library(tidyverse)
library(dplyr)
library(ggplot2)
library(plotly)

1. EXTRAER (extraer)

Cargamos el dataset de estudio

# instalamos el paquete
#install.packages("palmerpenguins")

# vemos el dataset (primeras 6 filas)
head(penguins)
##   species    island bill_len bill_dep flipper_len body_mass    sex year
## 1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
## 2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
## 3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
## 4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
## 5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
## 6  Adelie Torgersen     39.3     20.6         190      3650   male 2007
# podemos ver el dataset en forma horizontal
glimpse(penguins)
## Rows: 344
## Columns: 8
## $ species     <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Ad…
## $ island      <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgersen, Tor…
## $ bill_len    <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, …
## $ bill_dep    <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, …
## $ flipper_len <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180,…
## $ body_mass   <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, …
## $ sex         <fct> male, female, female, NA, female, male, female, male, NA, …
## $ year        <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
# Estructura del dataset
str(penguins)
## 'data.frame':    344 obs. of  8 variables:
##  $ species    : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ island     : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ bill_len   : num  39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
##  $ bill_dep   : num  18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
##  $ flipper_len: int  181 186 195 NA 193 190 181 195 193 190 ...
##  $ body_mass  : int  3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
##  $ sex        : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
##  $ year       : int  2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...

Notamos que tenemos 344 observaciones y 8 variables. Las variables categoricas ya son factores (especie, isla y genero), por lo que no tenemos que factorizarlas.

# Resumen estadistico
summary(penguins)
##       species          island       bill_len        bill_dep    
##  Adelie   :152   Biscoe   :168   Min.   :32.10   Min.   :13.10  
##  Chinstrap: 68   Dream    :124   1st Qu.:39.23   1st Qu.:15.60  
##  Gentoo   :124   Torgersen: 52   Median :44.45   Median :17.30  
##                                  Mean   :43.92   Mean   :17.15  
##                                  3rd Qu.:48.50   3rd Qu.:18.70  
##                                  Max.   :59.60   Max.   :21.50  
##                                  NA's   :2       NA's   :2      
##   flipper_len      body_mass        sex           year     
##  Min.   :172.0   Min.   :2700   female:165   Min.   :2007  
##  1st Qu.:190.0   1st Qu.:3550   male  :168   1st Qu.:2007  
##  Median :197.0   Median :4050   NA's  : 11   Median :2008  
##  Mean   :200.9   Mean   :4202                Mean   :2008  
##  3rd Qu.:213.0   3rd Qu.:4750                3rd Qu.:2009  
##  Max.   :231.0   Max.   :6300                Max.   :2009  
##  NA's   :2       NA's   :2
# Podemos obtener un resumen estadistico usando la libreria skimr
library(skimr)

skim(penguins)
Data summary
Name penguins
Number of rows 344
Number of columns 8
_______________________
Column type frequency:
factor 3
numeric 5
________________________
Group variables None

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
species 0 1.00 FALSE 3 Ade: 152, Gen: 124, Chi: 68
island 0 1.00 FALSE 3 Bis: 168, Dre: 124, Tor: 52
sex 11 0.97 FALSE 2 mal: 168, fem: 165

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
bill_len 2 0.99 43.92 5.46 32.1 39.23 44.45 48.5 59.6 ▃▇▇▆▁
bill_dep 2 0.99 17.15 1.97 13.1 15.60 17.30 18.7 21.5 ▅▅▇▇▂
flipper_len 2 0.99 200.92 14.06 172.0 190.00 197.00 213.0 231.0 ▂▇▃▅▂
body_mass 2 0.99 4201.75 801.95 2700.0 3550.00 4050.00 4750.0 6300.0 ▃▇▆▃▂
year 0 1.00 2008.03 0.82 2007.0 2007.00 2008.00 2009.0 2009.0 ▇▁▇▁▇

2. TRANSFORM (transformar)

Este es el paso mas importante. Un buen ETL asegura que los datos sean consistentes y listos para analizar.

Hay presencia de valores nulos. Debemos decidir que hacer con esos nulos, si omitirlos del análisis o reemplazarlos con algun otro valor, por ejemplo la mediana. Al tratarse de pocas observaciones, las eliminamos del dataframe.

# Eliminamos los NA
library(tidyr)

penguins_df <- drop_na(penguins)

str(penguins_df)
## 'data.frame':    333 obs. of  8 variables:
##  $ species    : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ island     : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ bill_len   : num  39.1 39.5 40.3 36.7 39.3 38.9 39.2 41.1 38.6 34.6 ...
##  $ bill_dep   : num  18.7 17.4 18 19.3 20.6 17.8 19.6 17.6 21.2 21.1 ...
##  $ flipper_len: int  181 186 195 193 190 181 195 182 191 198 ...
##  $ body_mass  : int  3750 3800 3250 3450 3650 3625 4675 3200 3800 4400 ...
##  $ sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 1 2 1 2 2 ...
##  $ year       : int  2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
# Cambiamos los nombres de las columnas
nuevos_nombres <- c("especie", "isla","longitud_pico", "ancho_pico", "longitud_aleta","peso_corporal", "genero", "año")

colnames(penguins_df) <- nuevos_nombres

# Mostramos los nombres de las columnas
colnames(penguins_df)
## [1] "especie"        "isla"           "longitud_pico"  "ancho_pico"    
## [5] "longitud_aleta" "peso_corporal"  "genero"         "año"
# podemos agrupar los datos y obtener estadisticas
penguins_df %>% group_by(especie) %>% summarise(promedio_long_pico = mean(longitud_pico), promedio_ancho_pico = mean(ancho_pico))
## # A tibble: 3 × 3
##   especie   promedio_long_pico promedio_ancho_pico
##   <fct>                  <dbl>               <dbl>
## 1 Adelie                  38.8                18.3
## 2 Chinstrap               48.8                18.4
## 3 Gentoo                  47.6                15.0
# Podemos realizar transformaciones a los datos.
# Transformar el peso corporal de gramos a kilos

penguins_df$peso_corporal <- penguins_df$peso_corporal / 1000
head(penguins_df)
##   especie      isla longitud_pico ancho_pico longitud_aleta peso_corporal
## 1  Adelie Torgersen          39.1       18.7            181         3.750
## 2  Adelie Torgersen          39.5       17.4            186         3.800
## 3  Adelie Torgersen          40.3       18.0            195         3.250
## 4  Adelie Torgersen          36.7       19.3            193         3.450
## 5  Adelie Torgersen          39.3       20.6            190         3.650
## 6  Adelie Torgersen          38.9       17.8            181         3.625
##   genero  año
## 1   male 2007
## 2 female 2007
## 3 female 2007
## 4 female 2007
## 5   male 2007
## 6 female 2007

3. LOAD (cargar)

Dejamos el dataset limpio para ser utilizado en analisis o modelos.

# Guardar el dataset transformado en formato csv
write.csv(penguins_df, "penguins_transformado.csv", row.names = FALSE)

Visualizaciones

# Grafico de cajas con ggplot2
# Agregamos titulo, subtitulo y nota al pie
graf_box <- ggplot(penguins_df, aes(x = especie, y = peso_corporal, fill = especie)) + 
  geom_boxplot() + 
  labs(title = "Boxplot peso corporal por especie", subtitle = "Pinguinos de la isla Palmer", caption = "Datos obtenidos entre 2007 y 2009") + 
  theme_minimal()

graf_box

# podemos combinar varias geom_functions en un mismo grafico
linea_tendencia <- ggplot(penguins_df) +
  geom_smooth(aes(x = longitud_aleta, y = peso_corporal)) +
  geom_point(aes(x = longitud_aleta, y = peso_corporal)) +
  labs(title = "Linea de tendencia. Longitud aleta vs Peso corporal") +
  theme_bw()

# y hacerlo interactivo con plotly
ggplotly(linea_tendencia)
# se pueden separar los graficos con facet para visualizar subconjuntos de datos
ggplotly(ggplot(penguins_df) +
  geom_point(aes(x = longitud_pico, y = peso_corporal, color = especie)) +
  facet_wrap(~especie) +
  labs(title = "Distribucion de Longitud de pico vs peso corporal por especie") +
  theme_bw())
# se puede utlizar facet_grid para armar una grilla con los graficos
ggplot(penguins_df)+
  geom_point(aes(x = longitud_aleta, y = peso_corporal, color = especie)) +
  facet_grid(genero~especie) +
  labs(title = "Distribucion de peso corporal vs longitud de aleta por especie y genero") +
  theme_bw()

# Mapa de calor
heatmap <- ggplotly(ggplot(penguins_df, aes(x = isla, y = especie)) +
  labs(title = "Mapa de calor. Especie vs Isla") +
  geom_bin2d())

heatmap